/**
 *  Author milkpotatoes
 */

#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <cstdio>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <queue>
#include <sstream>
#include <stdlib.h>
#include <string>
#include <thread>
#include <unistd.h>

#include "hilog_hooker.h"

struct LogInfo;
class LogQueue;

static LogLevel g_basicLogLevel = LogLevel::LOG_DEBUG;
static bool g_colorfulLog = false;
static bool g_fastLog = false;

static LogCallback g_logCallback = nullptr;
LogQueue *g_logQueue = nullptr;

constexpr const char *LOG_COLOR_NONE = "\x1b[0m";
constexpr const char *LOG_COLOR_DEBUG = "\x1b[2m";
constexpr const char *LOG_COLOR_INFO = LOG_COLOR_NONE;
constexpr const char *LOG_COLOR_WARN = "\x1b[33m";
constexpr const char *LOG_COLOR_ERROR = "\x1b[31m";
constexpr const char *LOG_COLOR_FATAL = "\x1b[31;2m";
constexpr const char *LOG_BLANK = "    ";

#define LOG_LEVEL_LIST(VV)                                                                                             \
    VV(DEBUG, Debug, debug);                                                                                           \
    VV(INFO, Info, info);                                                                                              \
    VV(WARN, Warn, warn);                                                                                              \
    VV(ERROR, Error, error);                                                                                           \
    VV(FATAL, Fatal, fatal)

const char *GetLevelName(LogLevel level) {
#define EXPAND_LEVEL(upper, _middle, _lower)                                                                           \
    case LogLevel::LOG_##upper:                                                                                        \
        return #upper

    switch (level) {
        LOG_LEVEL_LIST(EXPAND_LEVEL);
    default:
        return "UNKNOWN";
    }
#undef EXPAND_LEVEL
}

std::string GetTime(std::chrono::system_clock::time_point &timestamp) {
    auto time = std::chrono::system_clock::to_time_t(timestamp);
    std::tm buf;
    localtime_r(&time, &buf);
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp.time_since_epoch());
    std::ostringstream s;
    s << std::put_time(&buf, "%m-%d %H:%M:%S") << "." << std::setfill('0') << std::setw(3) << ms.count() % 1000;
    return s.str();
}

static void SetLogColor(std::ostringstream &oss, LogLevel level) {
#define COLOR_AUTO(upper, _m, _l)                                                                                      \
    case LogLevel::LOG_##upper:                                                                                        \
        oss << LOG_COLOR_##upper;                                                                                      \
        break;

    switch (level) {
        LOG_LEVEL_LIST(COLOR_AUTO)
    default:
        oss << LOG_COLOR_NONE;
        break;
    }
#undef COLOR_AUTO
}

class LogQueue {
public:
    void Send(LogInfo *info);
    LogInfo *Pop();
    LogInfo *PopUnsafety();
    bool IsEmpty();
    bool IsEmptyUnsafety();
    size_t Size();
    size_t SizeUnsafety();
    operator std::mutex &() { return mutex_; }
    void Wait(std::unique_lock<std::mutex> &lock);
    void NotifyOne();

private:
    std::mutex mutex_;
    std::condition_variable cond_;
    std::queue<LogInfo *> queue_;
};

void LogQueue::Send(LogInfo *info) {
    std::lock_guard<std::mutex> lock(mutex_);
    queue_.push(info);
}

LogInfo *LogQueue::Pop() {
    std::lock_guard<std::mutex> lock(mutex_);
    return PopUnsafety();
}

LogInfo *LogQueue::PopUnsafety() {
    if (queue_.empty()) {
        return nullptr;
    }
    LogInfo *result = queue_.front();
    queue_.pop();
    return result;
}

bool LogQueue::IsEmpty() {
    std::lock_guard<std::mutex> lock(mutex_);
    return IsEmptyUnsafety();
}

bool LogQueue::IsEmptyUnsafety() { return queue_.empty(); }

size_t LogQueue::Size() {
    std::lock_guard<std::mutex> lock(mutex_);
    return SizeUnsafety();
}

size_t LogQueue::SizeUnsafety() { return queue_.size(); }

void LogQueue::Wait(std::unique_lock<std::mutex> &lock) { cond_.wait(lock); }

void LogQueue::NotifyOne() { cond_.notify_one(); }

struct LogInfo {
    std::chrono::system_clock::time_point timestamp_;
    const pid_t tid_;
    const LogType type_;
    LogLevel level_;
    unsigned int domain_;
    std::string tag_;
    std::string message_;
    void Print();
};

void LogInfo::Print() {
    std::ostringstream oss;
    if (g_colorfulLog) {
        SetLogColor(oss, level_);
    }
    oss << GetTime(timestamp_);
    oss << LOG_BLANK << std::dec << getpid() << "-" << tid_ << LOG_BLANK << std::hex << domain_ << " "
        << std::setfill(' ') << std::setw(16) << std::left << tag_ << std::setfill(' ') << std::setw(5) << std::left
        << GetLevelName(level_) << LOG_BLANK << message_;
    if (g_colorfulLog) {
        oss << LOG_COLOR_NONE;
    }
    if (level_ >= LogLevel::LOG_WARN) {
        std::cerr << oss.str() << std::endl;
    } else {
        std::cout << oss.str() << std::endl;
    }
}

static void StripFormatString(const std::string &prefix, std::string &str) {
    for (auto pos = str.find(prefix, 0); pos != std::string::npos; pos = str.find(prefix, pos)) {
        str.erase(pos, prefix.size());
    }
}

extern "C" int HiLogPrintArgs(const LogType type, LogLevel level, unsigned int domain, const char *tag, const char *fmt,
                              va_list ap) {
    if (level < g_basicLogLevel) {
        return 0;
    }
    auto timestamp = std::chrono::system_clock::now();

    constexpr const uint32_t MAX_BUFFER_LENGTH = 4096;
    std::string buffer;
    buffer.resize(MAX_BUFFER_LENGTH);
    std::string newFmt(fmt);
    StripFormatString("{public}", newFmt);
    StripFormatString("{private}", newFmt);

    int msgLen = vsnprintf(const_cast<char *>(buffer.data()), buffer.size(), newFmt.c_str(), ap);
    buffer.resize(msgLen);

    if (g_logCallback) {
        g_logCallback(type, level, domain, tag, buffer.c_str());
    }

    if (g_fastLog) {
        LogInfo *info = new LogInfo{timestamp, gettid(), type, level, domain, tag, buffer};
        g_logQueue->Send(info);
        g_logQueue->NotifyOne();
    } else {
        LogInfo info{timestamp, gettid(), type, level, domain, tag, buffer};
        info.Print();
    }
    return 0;
}

#define LOG_VA_BODY(type, level, domain, tag, fmt)                                                                     \
    {                                                                                                                  \
        int ret;                                                                                                       \
        va_list ap;                                                                                                    \
        va_start(ap, (fmt));                                                                                           \
        ret = HiLogPrintArgs((type), (level), (domain), (tag), (fmt), ap);                                             \
        va_end(ap);                                                                                                    \
        return ret;                                                                                                    \
    }

extern "C" int OH_LOG_Print(LogType type, LogLevel level, unsigned int domain, const char *tag, const char *fmt, ...) {
    LOG_VA_BODY(type, level, domain, tag, fmt)
}
extern "C" int HiLogPrintDict(const LogType type, const LogLevel level, const unsigned int domain, const char *tag,
                              const FmtId *fmtid, const char *fmt, ...) {
    LOG_VA_BODY(type, level, domain, tag, fmt)
}
extern "C" int HiLogPrintDictNew(const LogType type, const LogLevel level, const unsigned int domain, const char *tag,
                                 const unsigned int uuid, const unsigned int fmtOffset, const char *fmt, ...) {
    LOG_VA_BODY(type, level, domain, tag, fmt)
}

extern "C" void LOG_SetCallback(LogCallback callback) { g_logCallback = callback; }

extern "C" __attribute__((constructor)) void LogHookerInit() {
    const char *colorful = getenv("LOG_COLORFUL");
    if (colorful) {
        g_colorfulLog = true;
    }
    const char *logFast = getenv("LOG_FAST");
    if (logFast) {
        g_fastLog = true;
    }

    g_logQueue = new LogQueue;

    if (logFast) {
        static std::atomic<bool> logDone{false};
        static std::thread logThread = std::thread([] {
            while (true) {
                LogInfo *info = nullptr;
                {
                    std::unique_lock<std::mutex> lock(*g_logQueue);
                    if (g_logQueue->IsEmptyUnsafety() && !logDone.load()) {
                        g_logQueue->Wait(lock);
                    }
                    info = g_logQueue->PopUnsafety();
                }
                if (info == nullptr)
                    break;
                info->Print();
                delete info;
                if (g_logQueue->IsEmpty() && logDone.load()) {
                    break;
                }
            }
        });
        atexit([] {
            logDone = true;
            g_logQueue->NotifyOne();
            if (logThread.joinable()) {
                logThread.join();
            }
            delete g_logQueue;
            g_logQueue = nullptr;
        });
    }

    const char *logLevel = getenv("LOG_LEVEL");
    g_basicLogLevel = LogLevel::LOG_ERROR;
    if (!logLevel) {
        return;
    }

    std::string level(logLevel);
#define INIT_LOG_LEVEL(upper, _, lower)                                                                                \
    if (level == #lower) {                                                                                             \
        g_basicLogLevel = LogLevel::LOG_##upper;                                                                       \
        return;                                                                                                        \
    }
    LOG_LEVEL_LIST(INIT_LOG_LEVEL);
#undef INIT_LOG_LEVEL
}

#undef LOG_LEVEL_LIST